﻿namespace FolderSync
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    using System.Threading;
    using System.Text.RegularExpressions;

    public enum CompoundWatcherChangeTypes
    {
        None,
        Created,
        Changed,
        Renamed,
        Deleted,
        ChNamed // changed AND renamed
    }

    public class FileEvent
    {
        public CompoundWatcherChangeTypes ChangeType;
        private string _fullPath;
        public string FullPath { get { return _fullPath; } set { _fullPath = value; } }
        private string _oldFullPath;
        public string OldFullPath { get { return _oldFullPath; } set { _oldFullPath = value; } }
        public string LogTag { get; set; }
        public Func<string, string> TranslatePath { get; set; }
        public int ErrorCount;
        public int key; // the sort key at which it is inserted in the event queue.
        public FileSystemEventArgs OriginalEvent;
        public FileSystemInfo FileSystemInfo;
        private string _folderName = null;
        public string FolderName
        {
            get
            {
                if (_folderName == null)
                {
                    var ma = new Regex(@"(.*)\\[^\\]+$").Match(_oldFullPath == null ? _fullPath : _oldFullPath);
                    if (ma.Success) _folderName = ma.Groups[1].Value;
                    else _folderName = string.Empty;
                }
                return _folderName;
            }
        }
        private string _fileName = null;
        public string FileName
        {
            get
            {
                if (_fileName == null)
                {
                    var ma = new Regex(@"\\([^\\]+)$").Match(_oldFullPath == null ? _fullPath : _oldFullPath);
                    if (ma.Success) _fileName = ma.Groups[1].Value;
                    else _fileName = string.Empty;
                }
                return _fileName;
            }
        }
        public DateTime EventTime = DateTime.Now;
        /// <summary>
        /// indicates the earliest date/time when an event may be processed.
        /// </summary>
        public DateTime Expiry = DateTime.Now;
        public FileEvent(Func<string, string> translatePath, FileSystemEventArgs evt)
        {
            if (translatePath == null) throw new ArgumentNullException("translatePath function not specified");
            TranslatePath = translatePath;
            //System.Diagnostics.Debug.Assert(evt != null);
            try
            {
                if (!Enum.TryParse(evt.ChangeType.ToString(), out this.ChangeType))
                {
                    throw new Exception("Unknown WatcherChangeTypes " + evt.ChangeType);
                }
                FullPath = evt.FullPath;
                if (evt is RenamedEventArgs) OldFullPath = ((RenamedEventArgs)evt).OldFullPath;
                this.key = -1;
                this.OriginalEvent = evt;
            }
            catch (Exception e)
            {
                System.Diagnostics.Debug.Assert(false, e.Message);
            }
        }
        public FileEvent(Func<string, string> translatePath) {
            if (translatePath == null) throw new ArgumentNullException("translatePath function not specified");
            TranslatePath = translatePath;
        }

        public override string ToString()
        {
            return
                string.Format("FileEvent [{0}]: ", this.key)
                + (OldFullPath != null
                    ? string.Format("{0} renamed to {1}, {2}", OldFullPath.PreFormat(), FullPath.PreFormat(), ChangeType)
                    : string.Format("{0} {1}", FullPath.PreFormat(), ChangeType)
                )
                + (ErrorCount > 0
                    ? string.Format("[{0}]", ErrorCount)
                    : string.Empty
                );
        }
        public static FileEvent operator +(FileEvent prev, FileEvent next)
        {
            // null is neutral for +
            if (next == null) return prev;
            if (prev == null) return next;

            // both events are not null: they must chain gracefully
            if (prev.EventTime > next.EventTime) {
                throw new ArgumentException("Cannot compose events in wrong time sequence");
            }
            if (!((
                (next.ChangeType == CompoundWatcherChangeTypes.Renamed ||
                next.ChangeType == CompoundWatcherChangeTypes.ChNamed &&
                prev.FullPath == next.OldFullPath)
                ) || (
                (next.ChangeType != CompoundWatcherChangeTypes.Renamed &&
                next.ChangeType != CompoundWatcherChangeTypes.ChNamed &&
                prev.FullPath == next.FullPath))))
            {
                throw new ArgumentException("Cannot compose events that do not concern same file");
            }
            if (next.ChangeType == CompoundWatcherChangeTypes.None) return prev;
            switch (prev.ChangeType)
            {
                case CompoundWatcherChangeTypes.Created:
                case CompoundWatcherChangeTypes.Changed:
                    switch (next.ChangeType)
                    {
                        case CompoundWatcherChangeTypes.Created:
                        case CompoundWatcherChangeTypes.Changed:
                            break;
                        case CompoundWatcherChangeTypes.Renamed:
                            next.ChangeType = CompoundWatcherChangeTypes.ChNamed;
                            next.OldFullPath = prev.FullPath;
                            break;
                        case CompoundWatcherChangeTypes.Deleted:
                            next.ChangeType = CompoundWatcherChangeTypes.None;
                            break;
                        case CompoundWatcherChangeTypes.ChNamed:
                            break;
                        case CompoundWatcherChangeTypes.None:
                        default:
                            next = prev;
                            break;
                    }
                    break;
                case CompoundWatcherChangeTypes.Renamed:
                    switch (next.ChangeType)
                    {
                        case CompoundWatcherChangeTypes.Created:
                        case CompoundWatcherChangeTypes.Changed:
                            next=prev;
                            next.ChangeType = CompoundWatcherChangeTypes.ChNamed;
                            break;
                        case CompoundWatcherChangeTypes.Renamed:
                        case CompoundWatcherChangeTypes.ChNamed:
                            next.OldFullPath = prev.OldFullPath;
                            break;
                        case CompoundWatcherChangeTypes.Deleted:
                            next.FullPath = prev.OldFullPath;
                            break;
                        case CompoundWatcherChangeTypes.None:
                        default:
                            next = prev;
                            break;
                    }
                    break;
                case CompoundWatcherChangeTypes.Deleted:
                    break;
                case CompoundWatcherChangeTypes.ChNamed:
                    switch (next.ChangeType)
                    {
                        case CompoundWatcherChangeTypes.Created:
                        case CompoundWatcherChangeTypes.Changed:
                            next = prev;
                            break;
                        case CompoundWatcherChangeTypes.Renamed:
                        case CompoundWatcherChangeTypes.ChNamed:
                            next.OldFullPath = prev.OldFullPath;
                            break;
                        case CompoundWatcherChangeTypes.Deleted:
                            next.FullPath = prev.OldFullPath;
                            break;
                        case CompoundWatcherChangeTypes.None:
                        default:
                            return prev;
                    }
                    break;
                case CompoundWatcherChangeTypes.None:
                default:
                    break;
            }
            return next;
        }
    }
}
